package com.lcy.struct.bTreePlus.disk.recovery.undo;

import com.lcy.struct.bTreePlus.disk.constants.BPlusPublicConstants;
import com.lcy.struct.bTreePlus.disk.file.FileUtil;
import com.lcy.struct.bTreePlus.utils.BplusUtils;
import lombok.SneakyThrows;

import java.nio.channels.FileChannel;
import java.util.*;

/**
 * @author lucongyang
 * @date Created in  9:41
 * @description
 */
class ReadUndoLog {
    /**
     * 读undo日志到缓存
     */
    @SneakyThrows
    LinkedHashMap<Long, List<String>> readUndo() {
        byte[] undoStartStrBytes = BPlusPublicConstants.UNDO_START_STR.getBytes();
        byte[] undoEndCommitStrBytes = BPlusPublicConstants.UNDO_END_COMMIT_STR.getBytes();
        byte[] undoContentContinueBytes = BPlusPublicConstants.UNDO_CONTENT_CONTINUE.getBytes();
        byte[] undoContentBytes = BPlusPublicConstants.UNDO_CONTENT.getBytes();
        FileChannel channel = UndoCenter.getInstance().getUndoChannel();
        long undoEndPosition = UndoCenter.getInstance().getUndoEndPosition();
        int startPosition = BPlusPublicConstants.HEAD_SIZE;
        if (channel.size() < startPosition) {
            return new LinkedHashMap<>();
        }
        int contentSize = BPlusPublicConstants.UNDO_PAGE_SIZE - BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH - undoContentBytes.length - undoContentContinueBytes.length;
        assert contentSize > 0;
        int size = Math.max(undoStartStrBytes.length + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH + undoEndCommitStrBytes.length,
                undoContentBytes.length + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH + undoContentContinueBytes.length);
        LinkedHashMap<Long, List<List<byte[]>>> unCommitMap = new LinkedHashMap<>();
        while (startPosition < undoEndPosition) {
            byte[] sizeStrBytes = FileUtil.getInstance().read(startPosition, size, channel);
            String sizeStr = new String(sizeStrBytes).trim();
            // 如果是开始段
            if (sizeStr.startsWith(BPlusPublicConstants.UNDO_START_STR)) {
                byte[] transactionIdBytes = Arrays.copyOfRange(sizeStrBytes, undoStartStrBytes.length, undoStartStrBytes.length + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH);
                long transactionId = Long.parseLong(new String(transactionIdBytes).trim());
                byte[] commitBytes = Arrays.copyOfRange(sizeStrBytes, undoStartStrBytes.length + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH,
                        undoStartStrBytes.length + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH + undoEndCommitStrBytes.length);
                String commitStr = new String(commitBytes);
                if (!BPlusPublicConstants.UNDO_END_COMMIT_STR.equals(commitStr)) {
                    // 如果未提交
                    unCommitMap.put(transactionId, new ArrayList<>());
                }
            }
            // 如果是内容段
            else if (sizeStr.startsWith(BPlusPublicConstants.UNDO_CONTENT)) {
                byte[] transactionIdBytes = Arrays.copyOfRange(sizeStrBytes, undoContentBytes.length, undoContentBytes.length + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH);
                long transactionId = Long.parseLong(new String(transactionIdBytes).trim());
                if (!unCommitMap.containsKey(transactionId)) {
                    startPosition += BPlusPublicConstants.UNDO_PAGE_SIZE;
                    continue;
                }
                byte[] contentBytes = FileUtil.getInstance().read(startPosition + undoContentBytes.length
                        + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH + undoContentContinueBytes.length, contentSize, channel);
                int start = undoContentBytes.length + BPlusPublicConstants.MAX_TRANSACTION_ID_LENGTH;
                int end = start + undoContentContinueBytes.length;
                // 如果是内容继续
                if (sizeStr.length() == end && BPlusPublicConstants.UNDO_CONTENT_CONTINUE.equals(sizeStr.substring(start, end))) {
                    List<List<byte[]>> listList = unCommitMap.get(transactionId);
                    listList.get(listList.size() - 1).add(contentBytes);
                }
                // 如果是下一条内容
                else {
                    List<byte[]> list = new ArrayList<>();
                    list.add(contentBytes);
                    unCommitMap.get(transactionId).add(list);
                }
            }

            startPosition += BPlusPublicConstants.UNDO_PAGE_SIZE;
        }

        LinkedHashMap<Long, List<String>> transactionUndoStrMap = new LinkedHashMap<>();
        for (Map.Entry<Long, List<List<byte[]>>> m : unCommitMap.entrySet()) {
            for (List<byte[]> contentList : m.getValue()) {
                int contentLength = contentList.stream().mapToInt(e -> e.length).sum();
                byte[] contentBytes = new byte[contentLength];
                int i = 0;
                for (byte[] bytes : contentList) {
                    BplusUtils.replaceArr(contentBytes, bytes, i);
                    i += bytes.length;
                }
                if (!transactionUndoStrMap.containsKey(m.getKey())) {
                    transactionUndoStrMap.put(m.getKey(), new ArrayList<>());
                }
                transactionUndoStrMap.get(m.getKey()).add(new String(contentBytes));
            }
        }

        return transactionUndoStrMap;
    }

    static ReadUndoLog getInstance() {
        return Singleton.INSTANCE;
    }

    private static class Singleton {
        private static final ReadUndoLog INSTANCE = new ReadUndoLog();
    }

    private ReadUndoLog() {
    }
}
